﻿using System;
using System.Collections.Generic;
using System.Windows;
using nRoute.Components.Routing;
using nRoute.Internal;
using nRoute.Navigation;
using nRoute.Navigation.Containers;

namespace nRoute.Controls
{
    public partial class StatefulBrowsingContainer
		 : IBrowsingContainer, IStatefulContainer
    {
        private readonly Stack<PageContentState> _backStack;
        private readonly Stack<PageContentState> _forwardStack;
        private readonly Dictionary<string, PageContentState> _states;

#region Constructor

        public StatefulBrowsingContainer()
        {
            _backStack = new Stack<PageContentState>();
            _forwardStack = new Stack<PageContentState>();
            _states = new Dictionary<string, PageContentState>(StringComparer.InvariantCultureIgnoreCase);
        }

#endregion

#region IBrowsingContainer Members

        public event EventHandler<NavigationContainerEventArgs> JournalPurged;

        public void NavigateBack()
        {
            OnNavigateBack();
        }

        public void NavigateForward()
        {
            OnNavigateForward();
        }

        public bool CanNavigateBack
        {
            get { return OnCanNavigateBack(); }
        }

        public bool CanNavigateForward
        {
            get { return OnCanNavigateForward(); }
        }

        public bool CanPurgeJournal
        {
            get { return (OnCanNavigateBack() || OnCanNavigateForward() || (_states != null && _states.Count > 0)); }
        }

        public void PurgeJournal()
        {
            OnPurgeJournals();
        }

        public IEnumerable<PageContentState> BackPagesJournal 
        {
            get
            {
                return _backStack;
            }
        }

        public IEnumerable<PageContentState> ForwardPagesJournal 
        {
            get
            {
                return _forwardStack;
            }
        } 

#endregion

#region IStatefulContainer Members

        public IEnumerable<PageContentState> NavigatedPagesJournal
        {
            get { return _states.Values; }
        }

        public bool HasState(string url)
        {
            if (string.IsNullOrEmpty(url)) return false;
            return _states.ContainsKey(url);
        }

#endregion

#region Overriden Methods

        protected override void OnProcessResponse(NavigationResponse response)
        {
            Guard.ArgumentNotNull(response, "response");

            // we won't handle it the response, if it is not the active request!
            if (!response.Request.Equals(base.ActiveNavigationRequest)) return;
            
            // we check if the navigation was successfull or not
            if (response.Status != ResponseStatus.Success)
            {
                // we raise navigation failed and return
                base.OnProcessResponse(response);
                return;
            }

            // note_ we don't send this request to the base class, hereon

            // we get the current page info
            var _currentPageStatelessInfo = GetCurrentPageStatelessContentState();

            // else as per the navigation mode we change the stacks
            switch (response.Request.NavigationMode)
            {
                // we treat new and unknown alike
                case NavigateMode.New:
                case NavigateMode.Unknown:

                    // we check if we were to record
                    _backStack.Push(_currentPageStatelessInfo);

                    // we also clear the forward stack
                    _forwardStack.Clear();

                    // and we say history changed
                    RaiseHistoryChanged();
                    break;

                case NavigateMode.Back:

                    // we push the current item into the forward stack
                    _forwardStack.Push(_currentPageStatelessInfo);

                    // we remove one item
                    _backStack.Pop();

                    // and we say history changed
                    RaiseHistoryChanged();
                    break;

                case NavigateMode.Forward:

                    // we push the current item into the back stack
                    _backStack.Push(_currentPageStatelessInfo);

                    // and we remove one item
                    _forwardStack.Pop();

                    // and we say history changed and return
                    RaiseHistoryChanged();
                    break;

                case NavigateMode.Refresh:

                    // we don't need to do anything here, as the history remains the same
                    break;
            }

            // we add to the journal the current page info
            if (base.CurrentNavigationRequest != null)
            {
                if (_states.ContainsKey(base.CurrentNavigationRequest.RequestUrl)) 
                {
                    _states[base.CurrentNavigationRequest.RequestUrl] = GetCurrentPageContentState();
                }
                else
                {
                    _states.Add(base.CurrentNavigationRequest.RequestUrl, GetCurrentPageContentState());
                    base.NotifyPropertyChanged(() => CanPurgeJournal);
                }
            }

            // we save our supporters and use them - note_ we need to save this to access later
            CurrentNavigationLifecycleSupporter = response.NavigationLifecycleSupporter;
            CurrentNavigationStateSupporter = response.NavigationStateSupporter;

            // if the content support navigation, we pass in the request/response merged parameters
            if (this.CurrentNavigationLifecycleSupporter != null) 
            {
                CurrentNavigationLifecycleSupporter.Initialize(response.ResponseParameters);
                Title = CurrentNavigationLifecycleSupporter.Title ??
                    nRoute.Navigation.Navigation.GetTitle(response.Content as DependencyObject);
            }
            else
            {
                Title = nRoute.Navigation.Navigation.GetTitle(response.Content as DependencyObject);
            }

            // if the content supports state, and we are navigating back/forward
            if (CurrentNavigationStateSupporter != null)
            {
                // restore state, if we have a state
                if (_states.ContainsKey(response.Request.RequestUrl))
                    CurrentNavigationStateSupporter.RestoreState(_states[response.Request.RequestUrl].State);  // note_ with stored state
            }

            // we save the url and the content
            CurrentNavigationRequest = response.Request;
            SetValue(ContentProperty, response.Content);

            // and raise the event navigation completed
            OnNavigationCompleted(new NavigatedEventArgs(this, response.Request));
        }

#endregion

#region Browsing Related Methods

        protected virtual void OnNavigateBack()
        {
            if (!CanNavigateBack) return;

            // we get the request
            var _backPageInfo = _backStack.Peek();
            var _request = new NavigationRequest(_backPageInfo.Url, _backPageInfo.State, NavigateMode.Back);

            // and we navigate, if the thing is not cancelled then
            // NOTE: Currently the navigate is synchronous so their are implications
            NavigationService.Navigate(_request, this);
        }

        protected virtual void OnNavigateForward()
        {
            
            if (!CanNavigateForward) return;

            // we get the request
            var _forwardPageInfo = _forwardStack.Peek();
            var _request = new NavigationRequest(_forwardPageInfo.Url, _forwardPageInfo.State, NavigateMode.Forward);

            // and we navigate, if the thing is not cancelled then
            // NOTE: Currently the navigate is synchrnous so their are implications
            NavigationService.Navigate(_request, this);
        }

        protected virtual bool OnCanNavigateBack()
        {
            return (_backStack.Count > 1);
        }

        protected virtual bool OnCanNavigateForward()
        {
            return (_forwardStack.Count > 0);
        }

        protected virtual void OnPurgeJournals()
        {
            if (!CanNavigateBack && !CanNavigateForward && _states.Count == 0) return;
            _states.Clear();

            // we clear
            _backStack.Clear();
            _forwardStack.Clear();

            // and we say history changed
            RaiseHistoryChanged();

            // we raise the event
            OnJournalsPurged(new NavigationContainerEventArgs(this));
        }

#endregion

#region Navigating Events Related

        protected virtual void OnJournalsPurged(NavigationContainerEventArgs e)
        {
            if (JournalPurged != null) JournalPurged(this, e);
        }

#endregion

#region Helpers

        private PageContentState GetCurrentPageContentState()
        {
            // we check the page has a url
            if (string.IsNullOrEmpty(this.Url)) return null;

            // we also add
            return new PageContentState(this.Url, this.Title, 
                (CurrentNavigationStateSupporter == null ? null : CurrentNavigationStateSupporter.SaveState()));
        }

        private PageContentState GetCurrentPageStatelessContentState()
        {
            // we check the page has a url
            if (string.IsNullOrEmpty(this.Url)) return null;

            // we also add
            return new PageContentState(this.Url, this.Title, null);
        }

        private void RaiseHistoryChanged()
        {
            base.NotifyPropertyChanged(() => CanNavigateBack);
            base.NotifyPropertyChanged(() => CanNavigateForward);
            base.NotifyPropertyChanged(() => CanPurgeJournal);
        }

#endregion

    }
}
